/*
 * Copyright (c) 2011 Ayan Dave http://www.linkedin.com/in/daveayan
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 * associated documentation files (the "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
 * following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial 
 * portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package testrj.domain;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

import testrj.util.ReflectionUtils;

public class MethodToTest {
	private String objectUnderTest;
	private transient Object objectForObjectUnderTest;
	private String methodName;
	private List<String> inputParamTypes = new ArrayList<String>();
	private String returnType = "void";
	private transient TestCase testcase = new TestCase();
	private List<TestCase> allTestCases = new ArrayList<TestCase>();

	public Object getReturnValueAsOriginalObject() {
		return TestrjUtils.transformObjectTo(testcase.getReturnValue().getParameter(), returnType);
	}
	
	public List<Object> getInputParamsAsOrignialObjects() {
		return Lists.transform(testcase.getInputParams(), new Function<DomainObject, Object>() {
			int i = 0;
			public Object apply(DomainObject param) {
				return TestrjUtils.transformObjectTo(param.getParameter(), inputParamTypes.get(i++));
			}
		});
	}

	public void prepareToRunTestCaseId(int id) {
		if (id - 1 >= allTestCases.size())
			return;
		testcase.prepareForRun(allTestCases.get(id - 1));
	}

	public static MethodToTest init(String className, String methodName, Object thisObject, Object[] inputParams) {
//		String referenceName = TestrjUtils.getReferenceNameFor(className, methodName, inputParams);
//		MethodToTest existingMethodToTestRecord = TestrjConfig.persister.getSingleTestDataSet(referenceName);
//		if (existingMethodToTestRecord != null) {
//			existingMethodToTestRecord.record(className, methodName, thisObject, inputParams);
//			return existingMethodToTestRecord;
//		}
		return new MethodToTest(className, methodName, thisObject, inputParams);
	}

	private MethodToTest() {
	}

	private MethodToTest(String className, String methodName, Object thisObject, Object[] inputParams) {
		record(className, methodName, thisObject, inputParams);
	}

	public void record(String className, String methodName, Object thisObject, Object[] inputParams) {
		this.setObjectUnderTest(className);
		this.setMethodName(methodName);
		
		testcase.setCurrentObject(DomainObject.asDomainObject(thisObject));
		testcase.setInputParams(DomainObject.listFromArrayOfParameters(inputParams));
	}

	public void recordReturnValueAndPersist(Object returnValue) {
		testcase.setReturnValue(DomainObject.asDomainObject(returnValue));
		inputParamTypes = TestrjUtils.getListOfParameterClassAsString(currentTestCase().getInputParams());
		returnType = currentTestCase().getReturnValue().className();
		persist();
	}

	public void persist() {
		updateTestCount();
		testcase.persistInputParams();
		testcase.persistGlobalParams();
		testcase.persistReturnValue();
		TestrjConfig.persister.persistTest(this);
	}

	private void updateTestCount() {
		testcase.setId(allTestCases.size() + 1 + "");
	}

	public List<TestCase> getAllTestCases() {
		return allTestCases;
	}

	public void setAllTestCases(List<TestCase> allTestCases) {
		this.allTestCases = allTestCases;
	}

	public Object getObjectForObjectUnderTest() {
		this.objectForObjectUnderTest = testcase.getCurrentObject().getParameter();
		if (testcase.getCurrentObject().isNull()) {
			return ReflectionUtils.getInstanceOfClassForcibly(getObjectUnderTest());
		}
		return this.objectForObjectUnderTest;
	}

	public Method getMethodToTest() {
		try {
			return objectForObjectUnderTest.getClass().getMethod(getMethodName(), getInputParamsAsClasses().toArray(new Class<?>[] {}));
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	
	private List<Class<?>> getInputParamsAsClasses() {
		return Lists.transform(inputParamTypes, new Function<String, Class<?>>() {
			public Class<?> apply(String type) {
				return ReflectionUtils.getClassForClassName(type);
			}
		});
	}

	public String getObjectUnderTest() {
		return objectUnderTest;
	}

	public void setObjectUnderTest(String objectUnderTest) {
		this.objectUnderTest = objectUnderTest;
	}

	public String getMethodName() {
		return methodName;
	}

	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}

	public TestCase currentTestCase() {
		return testcase;
	}

	
	public List<String> getInputParamTypes() {
		return inputParamTypes;
	}

	public void setInputParamTypes(List<String> inputParamTypes) {
		this.inputParamTypes = inputParamTypes;
	}

	public String getReturnType() {
		return returnType;
	}

	public void setReturnType(String returnType) {
		this.returnType = returnType;
	}
}